#pragma once

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
#include "range.h"


namespace sysy
{
    /*!
     * \defgroup type Types
     * The SysY type system is quite simple.
     * 1. The base class `Type` is used to represent all primitive scalar types,
     * include `int`, `float`, `void`, and the label type representing branch
     * targets.
     * 2. `PointerType` and `FunctionType` derive from `Type` and represent pointer
     * type and function type, respectively.
     *
     * NOTE `Type` and its derived classes have their ctors(构造函数) declared as
     * 'protected'. Users must use Type::getXXXType() methods to obtain `Type`
     * pointers.
     * @{
     */

    /*!
     * `Type` is used to represent all primitive scalar types,
     * include `int`, `float`, `void`, and the label type representing branch
     * targets
     */

    // 如何表示array: pointer -> pointer -> i32/float
    class Type
    {
    public:
        enum Kind
        {
            kInt,
            kFloat,
            kVoid,
            kLabel,
            kPointer,
            kFunction,
            kArray
        };

        Kind kind;

    protected:
        Type(Kind kind) : kind(kind) {}

        virtual ~Type() {}

    public:
        static Type *getIntType(int);
        static Type *getFloatType();
        static Type *getVoidType();
        static Type *getLabelType();
        static Type *getPointerType(Type *baseType);
        static Type *getFunctionType(Type *returnType, const std::vector<Type *> &paramTypes = {});
        static Type *getArrayType(Type *baseType, const std::vector<int> &dims);

    public:
        Kind getKind() const { return kind; }

        bool isInt() const { return kind == kInt; }

        bool isFloat() const { return kind == kFloat; }

        bool isVoid() const { return kind == kVoid; }

        bool isLabel() const { return kind == kLabel; }

        bool isPointer() const { return kind == kPointer; }

        bool isFunction() const { return kind == kFunction; }


        bool isArray() const { return kind == kArray; }

        int getSize() const;

        // 作用: 将Type类型的指针转换为子类指针
        template <typename T>
        std::enable_if_t<std::is_base_of_v<Type, T>, T *> as() const
        {
            return dynamic_cast<T *>(const_cast<Type *>(this));
        }

        virtual const std::string toString()
        {
            switch (kind)
            {
            case kFloat:
                return "float";
            case kVoid:
                return "void";
            default:
                return "unimplemented";
            }
        }
    }; // class Type


    class IntType : public Type
    {
    public:
        int bits;
        [[nodiscard]] int getBits() const { return bits; }

        IntType(int bits = 32) : Type(kInt), bits(bits) {}

        const std::string toString() override
        {
            switch (bits)
            {
            case 1:
                return "i1";
            case 8:
                return "i8";
            case 32:
                return "i32";
            default:
                assert(false);
            }
        };
    };


    class ArrayType : public Type
    {
    protected:
        Type *baseType;
        // dims每个元素一定大于0
        // 作为为函数参数时, 直接处理成指向从第二维开始的指针
        const std::vector<int> dims;

    public:
        static ArrayType *get(Type *baseType, const std::vector<int> &dims);

        ArrayType(Type *baseType, const std::vector<int> &dims) : Type(kArray), baseType(baseType), dims(dims) {}

    public:
        Type *getBaseType() const { return baseType; }
        const std::vector<int> &getDims() const { return dims; }
        const std::string toString() override;
    };

    //! Pointer type
    class PointerType : public Type
    {
    protected:
        Type *baseType;

    protected:
        PointerType(Type *baseType) : Type(kPointer), baseType(baseType) {}

    public:
        static PointerType *get(Type *baseType);

    public:
        Type *getBaseType() const { return baseType; }

        const std::string toString() override;
    }; // class PointerType

    //! Function type
    class FunctionType : public Type
    {
    private:
        Type *returnType;
        std::vector<Type *> paramTypes;

    protected:
        FunctionType(Type *returnType, const std::vector<Type *> &paramTypes = {}) :
            Type(kFunction), returnType(returnType), paramTypes(paramTypes)
        {
        }

    public:
        static FunctionType *get(Type *returnType, const std::vector<Type *> &paramTypes = {});

    public:
        Type *getReturnType() const { return returnType; }

        // auto getParamTypes() const
        // {
        //     return make_range(paramTypes);
        // }

        auto &getParamTypes() const { return paramTypes; }

        int getNumParams() const { return paramTypes.size(); }
    }; // class FunctionType

    /*!
     * @}
     */

    /*!
     * \defgroup ir IR
     *
     * The SysY IR is an instruction level language. The IR is organized
     * as a four-level tree structure, as shown below
     *
     * \dotfile ir-4level.dot IR Structure
     *
     * - `Module` corresponds to the top level "CompUnit" syntax structure
     * - `GlobalValue` corresponds to the "Decl" syntax structure
     * - `Function` corresponds to the "FuncDef" syntax structure
     * - `BasicBlock` is a sequence of instructions without branching. A `Function`
     *   made up by one or more `BasicBlock`s.
     * - `Instruction` represents a primitive operation on values, e.g., add or sub.
     *
     * The fundamental data concept in SysY IR is `Value`. A `Value` is like
     * a register and is used by `Instruction`s as input/output operand. Each value
     * has an associated `Type` indicating the data type held by the value.
     *
     * Most `Instruction`s have a three-address signature, i.e., there are at most 2
     * input values and at most 1 output value.
     *
     * The SysY IR adopts a Static-Single-Assignment (SSA) design. That is, `Value`
     * is defined (as the output operand ) by some instruction, and used (as the
     * input operand) by other instructions. While a value can be used by multiple
     * instructions, the `definition` occurs only once. As a result, there is a
     * one-to-one relation between a value and the instruction defining it. In other
     * words, any instruction defines a value can be viewed as the defined value
     * itself. So `Instruction` is also a `Value` in SysY IR. See `Value` for the
     * type hierarchy.
     *
     * @{
     */

    class User;
    class Value;

    //! `Use` represents the relation between a `Value` and its `User`
    class Use
    {
    private:
        //! the position of value in the user's operands, i.e.,
        //! user->getOperands[index] == value
        int index;
        User *user;
        Value *value;

    public:
        Use() = default;

        Use(int index, User *user, Value *value) : index(index), user(user), value(value) {}

    public:
        int getIndex() const { return index; }

        User *getUser() const { return user; }

        Value *getValue() const { return value; }

        void setValue(Value *value) { this->value = value; }
    }; // class Use

    //! The base class of all value types
    class Value
    {
    protected:
        Type *type;
        std::string name;
        std::list<Use *> uses;

    protected:
        Value(Type *type, const std::string &name = "") : type(type), name(name), uses() {}

        virtual ~Value() {}

    public:
        Type *getType() const { return type; }

        bool isInt() const { return type->isInt(); }

        bool isFloat() const { return type->isFloat(); }

        bool isPointer() const { return type->isPointer(); }

        bool isBool() const
        {
            auto intt = type->as<IntType>();
            if (intt && intt->getBits() == 1)
                return true;
            return false;
        }


        const std::list<Use *> &getUses() { return uses; }

        void addUse(Use *use) { uses.push_back(use); }

        void replaceAllUsesWith(Value *value);

        void removeUse(Use *use) { uses.remove(use); }

        const auto &getName() { return name; }

        void setName(std::string name) { this->name = name; }

        virtual const std::string toString() { return "[Some value]"; }
    }; // class Value

    /*!
     * Static constants known at compile time.
     *
     * `ConstantValue`s are not defined by instructions, and do not use any other
     * `Value`s. It's type is either `int` or `float`.
     */
    class ConstantValue : public Value
    {
    protected:
        union
        {
            int iScalar;
            float fScalar;
        };

    protected:
        // 禁用外部构造, 方便配合下方常量池
        ConstantValue(int value, const std::string &name = "", const int bits = 32) :
            Value(Type::getIntType(bits), name), iScalar(value)
        {
        }

        ConstantValue(float value, const std::string &name = "") : Value(Type::getFloatType(), name), fScalar(value) {}

    public:
        // 这里在内部维护了常量池, 不会出现同时存在大量相同值的情况
        // 取消默认参数, 强制命名
        // 为了后续生成ir方便, 可以将常量名直接作为实际的值使用
        static ConstantValue *get(int value, const std::string &name, int bits);
        static ConstantValue *get(float value, const std::string &name);

        // 因为设计问题, 获取int类型的constant value需要多传一个参数
        // 导致假如手滑忘掉最后一个参数, 可能调用的就是获取float的constvalue函数
        // 因此使用模板删掉除float外所有重载
        template <typename T>
        static ConstantValue *get(T v, const std::string &name) = delete;

    public:
        int getInt() const
        {
            assert(isInt());
            return iScalar;
        }

        float getFloat() const
        {
            assert(isFloat());
            return fScalar;
        }

        const std::string toString() override;
    }; // class ConstantValue

    class BasicBlock;
    /*!
     * Arguments of `BasicBlock`s.
     *
     * SysY IR is an SSA language, however, it does not use PHI instructions as in
     * LLVM IR. `Value`s from different predecessor blocks are passed explicitly as
     * block arguments. This is also the approach used by MLIR.
     * NOTE that `Function` does not own `Argument`s, function arguments are
     * implemented as its entry block's arguments.
     */

    class Argument : public Value
    {
    protected:
        BasicBlock *block;
        int index;

    public:
        Argument(Type *type, const std::string &name = "") : Value(type, name) {}
    };

    class Instruction;
    class Function;
    /*!
     * The container for `Instruction` sequence.
     *
     * `BasicBlock` maintains a list of `Instruction`s, with the last one being
     * a terminator (branch or return). Besides, `BasicBlock` stores its arguments
     * and records its predecessor and successor `BasicBlock`s.
     */
    class BasicBlock : public Value
    {
        friend class Function;
        friend class IRBuilder; ///< 处理全局声明时不在Function中，必须通过IRBuilder创建第一个基本块

    public:
        using inst_list = std::list<std::unique_ptr<Instruction>>;
        using iterator = inst_list::iterator;
        using block_list = std::vector<BasicBlock *>;

    protected:
        Function *parent;
        inst_list instructions;
        block_list successors;
        block_list predecessors;

    protected:
        explicit BasicBlock(Function *parent, const std::string &name = "") :
            Value(Type::getLabelType(), name), parent(parent), instructions(), successors(), predecessors()
        {
        }

    public:
        int getNumInstructions() const { return instructions.size(); }

        auto getLastInst() { return instructions.back().get(); }


        int getNumPredecessors() const { return predecessors.size(); }

        int getNumSuccessors() const { return successors.size(); }

        Function *getParent() const { return parent; }

        inst_list &getInstructions() { return instructions; }


        block_list &getPredecessors() { return predecessors; }

        block_list &getSuccessors() { return successors; }

        iterator begin() { return instructions.begin(); }

        iterator end() { return instructions.end(); }

        iterator terminator() { return std::prev(end()); }

        bool isEmptyBBlock() const { return instructions.empty(); }
    }; // class BasicBlock

    //! User is the abstract base type of `Value` types which use other `Value` as
    //! operands. Currently, there are two kinds of `User`s, `Instruction` and
    //! `GlobalValue`.
    class User : public Value
    {
    protected:
        std::vector<Use> operands;

    protected:
        User(Type *type, const std::string &name = "") : Value(type, name), operands() {}

    public:
        struct operand_iterator : std::vector<Use>::iterator
        {
            using Base = std::vector<Use>::iterator;
            using Base::Base;
            using value_type = Value *;

            value_type operator->() { return operator*().getValue(); }
        };

    public:
        int getNumOperands() const { return operands.size(); }

        auto operand_begin() const { return operands.begin(); }

        auto operand_end() const { return operands.end(); }

        auto getOperands() const { return make_range(operand_begin(), operand_end()); }

        Value *getOperand(int index) const { return operands[index].getValue(); }

        void addOperand(Value *value)
        {
            operands.emplace_back(operands.size(), this, value);
            value->addUse(&operands.back());
        }

        template <typename ContainerT>
        void addOperands(const ContainerT &operands)
        {
            for (auto value : operands)
                addOperand(value);
        }

        void replaceOperand(int index, Value *value);
        void setOperand(int index, Value *value);

        const std::string &getName() const { return name; }
    }; // class User

    /*!
     * Base of all concrete instruction types.
     */
    class Instruction : public User
    {
    public:
        enum Kind : uint64_t
        {
            kInvalid = 0x0UL,
            // Binary
            kAdd = 0x1UL << 0,
            kSub = 0x1UL << 1,
            kMul = 0x1UL << 2,
            kDiv = 0x1UL << 3,
            kRem = 0x1UL << 4,
            kICmpEQ = 0x1UL << 5,
            kICmpNE = 0x1UL << 6,
            kICmpLT = 0x1UL << 7,
            kICmpGT = 0x1UL << 8,
            kICmpLE = 0x1UL << 9,
            kICmpGE = 0x1UL << 10,
            kFAdd = 0x1UL << 14,
            kFSub = 0x1UL << 15,
            kFMul = 0x1UL << 16,
            kFDiv = 0x1UL << 17,
            kFRem = 0x1UL << 18,
            kFCmpEQ = 0x1UL << 19,
            kFCmpNE = 0x1UL << 20,
            kFCmpLT = 0x1UL << 21,
            kFCmpGT = 0x1UL << 22,
            kFCmpLE = 0x1UL << 23,
            kFCmpGE = 0x1UL << 24,
            // Unary
            kNeg = 0x1UL << 25,
            kNot = 0x1UL << 26,
            kFNeg = 0x1UL << 27,
            kFtoI = 0x1UL << 28,
            kIToF = 0x1UL << 29,
            // call
            kCall = 0x1UL << 30,
            // terminator
            kCondBr = 0x1UL << 31,
            kBr = 0x1UL << 32,
            kReturn = 0x1UL << 33,
            // mem op
            kAlloca = 0x1UL << 34,
            kLoad = 0x1UL << 35,
            kStore = 0x1UL << 36,
            // constant
            // kConstant = 0x1UL << 37,
            // extra
            // zero extension, as unary op, only i1 to i32
            kZext = 0x1UL << 37,
            kGEP = 0x1UL << 38,
        };

    protected:
        Kind kind;
        BasicBlock *parent;

    protected:
        Instruction(Kind kind, Type *type, BasicBlock *parent = nullptr, const std::string &name = "") :
            User(type, name), kind(kind), parent(parent)
        {
        }

    public:
        Kind getKind() const { return kind; }

        BasicBlock *getParent() const { return parent; }

        void setParent(BasicBlock *bb) { parent = bb; }

        bool isBinary() const
        {
            static constexpr uint64_t BinaryOpMask = (kAdd | kSub | kMul | kDiv | kRem) |
                (kICmpEQ | kICmpNE | kICmpLT | kICmpGT | kICmpLE | kICmpGE) | (kFAdd | kFSub | kFMul | kFDiv | kFRem) |
                (kFCmpEQ | kFCmpNE | kFCmpLT | kFCmpGT | kFCmpLE | kFCmpGE);
            return kind & BinaryOpMask;
        }

        bool isUnary() const
        {
            static constexpr uint64_t UnaryOpMask = kNeg | kNot | kFNeg | kFtoI | kIToF | kZext;
            return kind & UnaryOpMask;
        }

        bool isMemory() const
        {
            static constexpr uint64_t MemoryOpMask = kAlloca | kLoad | kStore;
            return kind & MemoryOpMask;
        }

        bool isAlloca() const { return kind == kAlloca; }

        bool isLoad() const { return kind == kLoad; }

        bool isStore() const { return kind == kStore; }

        bool isGEP() const { return kind == kGEP; }

        bool isTerminator() const
        {
            static constexpr uint64_t TerminatorOpMask = kCondBr | kBr | kReturn;
            return kind & TerminatorOpMask;
        }

        bool isRet() const { return kind == kReturn; }

        bool isCmp() const
        {
            static constexpr uint64_t CmpOpMask = (kICmpEQ | kICmpNE | kICmpLT | kICmpGT | kICmpLE | kICmpGE) |
                (kFCmpEQ | kFCmpNE | kFCmpLT | kFCmpGT | kFCmpLE | kFCmpGE);
            return kind & CmpOpMask;
        }

        bool isRetTypeI1() const
        {
            static constexpr uint64_t retTypeI1Mask = (kICmpEQ | kICmpNE | kICmpLT | kICmpGT | kICmpLE | kICmpGE) |
                (kFCmpEQ | kFCmpNE | kFCmpLT | kFCmpGT | kFCmpLE | kFCmpGE) | kNot;
            return kind & retTypeI1Mask;
        };

        bool isBranch() const
        {
            static constexpr uint64_t BranchOpMask = kBr | kCondBr;
            return kind & BranchOpMask;
        }

        bool isCommutative() const
        {
            static constexpr uint64_t CommutativeOpMask =
                kAdd | kMul | kICmpEQ | kICmpNE | kFAdd | kFMul | kFCmpEQ | kFCmpNE;
            return kind & CommutativeOpMask;
        }

        bool isUnconditional() const { return kind == kBr; }

        bool isConditional() const { return kind == kCondBr; }


        bool noRegName() const
        {
            static constexpr uint64_t noRegName = kStore | kCondBr;
            return kind & noRegName;
        }

        virtual const std::string toString() { return "[Some Instruction]"; }
    }; // class Instruction

    class Function;

    //! Function call.
    class CallInst : public Instruction
    {
        friend class IRBuilder;

    protected:
        CallInst(Function *callee, const std::vector<Value *> args = {}, BasicBlock *parent = nullptr,
                 const std::string &name = "");

    public:
        Function *getCallee();

        auto getArguments() { return make_range(std::next(operand_begin()), operand_end()); }

        const std::string toString() override;
    }; // class CallInst

    //! Unary instruction, includes '!', '-' and type conversion.
    class UnaryInst : public Instruction
    {
        friend class IRBuilder;
        static Type *getUnaryInstType(Kind kind, Type *inType)
        {
            switch (kind)
            {
            default:
                return inType;
            }
        }

    protected:
        UnaryInst(Kind kind, Type *type, Value *operand, BasicBlock *parent = nullptr, const std::string &name = "") :
            Instruction(kind, getUnaryInstType(kind, type), parent, name)
        {
            addOperand(operand);
        }

    public:
        Value *getOperand() const { return User::getOperand(0); }

        const std::string toString() override;
    }; // class UnaryInst

    //! Binary instruction, e.g., arithmetic, relation, logic, etc.
    class BinaryInst : public Instruction
    {
        friend class IRBuilder;
        static Type *getBinaryInstType(Kind kind, Type *inType)
        {
            switch (kind)
            {
            case kFtoI:
            case kZext:
                return Type::getIntType(32);
            case kIToF:
                return Type::getFloatType();
            case kFCmpEQ:
            case kFCmpNE:
            case kFCmpLT:
            case kFCmpGT:
            case kFCmpLE:
            case kFCmpGE:
            case kICmpEQ:
            case kICmpNE:
            case kICmpLT:
            case kICmpGT:
            case kICmpLE:
                return Type::getIntType(1);
            default:
                return inType;
            }
        }

    protected:
        BinaryInst(Kind kind, Type *type, Value *lhs, Value *rhs, BasicBlock *parent, const std::string &name = "") :
            Instruction(kind, getBinaryInstType(kind, type), parent, name)
        {
            addOperand(lhs);
            addOperand(rhs);
        }

    public:
        Value *getLhs() const { return getOperand(0); }

        Value *getRhs() const { return getOperand(1); }

        const std::string toString() override
        {
            std::stringstream ss;
            ss << name << " = ";
            switch (kind)
            {
            case Instruction::kICmpEQ:
                ss << "icmp eq ";
                break;
            case Instruction::kICmpNE:
                ss << "icmp ne ";
                break;
            case Instruction::kICmpGE:
                ss << "icmp sge ";
                break;
            case Instruction::kICmpGT:
                ss << "icmp sgt ";
                break;
            case Instruction::kICmpLE:
                ss << "icmp sle ";
                break;
            case Instruction::kICmpLT:
                ss << "icmp slt ";
                break;
            case Instruction::kAdd:
                ss << "add ";
                break;
            case Instruction::kMul:
                ss << "mul ";
                break;
            case Instruction::kSub:
                ss << "sub ";
                break;
            case Instruction::kDiv:
                ss << "sdiv ";
                break;
            case Instruction::kRem:
                ss << "srem ";
                break;
            case kFCmpEQ:
                ss << "fcmp oeq ";
                break;
            case kFCmpNE:
                ss << "fcmp one ";
                break;
            case Instruction::kFCmpGE:
                ss << "fcmp oge ";
                break;
            case Instruction::kFCmpGT:
                ss << "fcmp ogt ";
                break;
            case Instruction::kFCmpLE:
                ss << "fcmp ole ";
                break;
            case Instruction::kFCmpLT:
                ss << "fcmp olt ";
                break;
            case kFAdd:
                ss << "fadd ";
                break;
            case kFSub:
                ss << "fsub ";
                break;
            case kFMul:
                ss << "fmul ";
                break;
            case kFDiv:
                ss << "fdiv ";
                break;

            default:
                ss << "[unimplemented binary op]";
                break;
            }
            ss << operands[0].getValue()->getType()->toString() << " ";
            ss << operands[0].getValue()->getName() << ", " << operands[1].getValue()->getName();
            return ss.str();
        }
    }; // class BinaryInst

    //! The return statement
    class ReturnInst : public Instruction
    {
        friend class IRBuilder;

    protected:
        ReturnInst(Value *value = nullptr, BasicBlock *parent = nullptr) :
            Instruction(kReturn, Type::getVoidType(), parent, "")
        {
            if (value)
                addOperand(value);
        }

    public:
        bool hasReturnValue() const { return not operands.empty(); }

        Value *getReturnValue() const { return hasReturnValue() ? getOperand(0) : nullptr; }

        const std::string toString() override
        {
            std::stringstream ss;
            if (!operands.empty())
                ss << "ret " << operands[0].getValue()->getType()->toString() << " "
                   << operands[0].getValue()->getName();
            else
                ss << "ret void";
            return ss.str();
        }
    }; // class ReturnInst

    //! Unconditional branch
    class UncondBrInst : public Instruction
    {
        friend class IRBuilder;

    protected:
        UncondBrInst(BasicBlock *block, BasicBlock *parent = nullptr) :
            Instruction(kCondBr, Type::getVoidType(), parent, "")
        {
            addOperand(block);
        }

    public:
        BasicBlock *getBlock() const { return dynamic_cast<BasicBlock *>(getOperand(0)); }

        auto getArguments() const { return make_range(std::next(operands.begin()), operands.end()); }

        const std::string toString() override { return "br label %" + operands[0].getValue()->getName(); }
    }; // class UncondBrInst

    //! Conditional branch
    class CondBrInst : public Instruction
    {
        friend class IRBuilder;

    protected:
        CondBrInst(Value *condition, BasicBlock *thenBlock, BasicBlock *elseBlock,

                   BasicBlock *parent = nullptr) : Instruction(kCondBr, Type::getVoidType(), parent, "")
        {
            addOperand(condition);
            addOperand(thenBlock);
            addOperand(elseBlock);
        }

    public:
        BasicBlock *getThenBlock() const { return dynamic_cast<BasicBlock *>(getOperand(0)); }

        BasicBlock *getElseBlock() const { return dynamic_cast<BasicBlock *>(getOperand(1)); }


        const std::string toString() override
        {
            std::stringstream ss;
            ss << "br i1 " << operands[0].getValue()->getName() << ", "
               << "label %" << operands[1].getValue()->getName() << ", label %" << operands[2].getValue()->getName();
            return ss.str();
        }
    }; // class CondBrInst

    //! Allocate memory for stack variables, used for non-global variable
    //! declaration
    class AllocaInst : public Instruction
    {
        friend class IRBuilder;

    protected:
        AllocaInst(Type *type, const std::vector<Value *> &dims = {}, BasicBlock *parent = nullptr,
                   const std::string &name = "", bool isConst = false, int arraySize = 0) :
            Instruction(kAlloca, type, parent, name), isConst(isConst), nDims(dims.size()), arraySize(arraySize),
            intArrayData(nullptr), floatArrayData(nullptr)
        {
            addOperands(dims);
            if (nDims != 0)
            {
                if (type->as<PointerType>()->getBaseType()->as<ArrayType>()->getBaseType()->isInt())
                {
                    intArrayData = new int[arraySize];
                    memset(intArrayData, 0, arraySize * sizeof(int));
                }
                else
                {
                    floatArrayData = new float[arraySize];
                    memset(floatArrayData, 0, arraySize * sizeof(float));
                }
            }
            else
            {
                // if (type->isInt())
                // intScalarValue = dynamic_cast<ConstantValue*>()
                // TODO: alloca标量类型初始化?
            }
        }

    protected:
        bool isConst;
        // 数组的维数, 可以通过这个判断是数组还是标量
        int nDims;
        // 数组大小
        int arraySize;
        // 数组指针
        int *intArrayData;
        float *floatArrayData;
        // 标量值
        int intScalarValue;
        float floatScalarValue;

    public:
        int getNumDims() const { return getNumOperands(); }

        auto getDims() const { return getOperands(); }

        Value *getDim(int index) { return getOperand(index); }

        const std::string toString() override
        {
            std::stringstream ss;
            ss << name << " = "
               << "alloca ";

            if (nDims == 0)
            {
                ss << type->as<PointerType>()->getBaseType()->toString();
            }
            else
            {
                ss << getArrayTypeStr();
            }
            return ss.str();
        }

        bool isconst() { return isConst; }

        void setInt(int v, const std::vector<Value *> &indices = {})
        {
            if (indices.empty())
            {
                intScalarValue = v;
            }
            else
            {
                int offset = 0, factor = 1;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                intArrayData[offset] = v;
            }
        }

        void setFloat(float v, const std::vector<Value *> &indices = {})
        {
            if (indices.empty())
            {
                floatScalarValue = v;
            }
            else
            {
                int offset = 0, factor = 1;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                floatArrayData[offset] = v;
            }
        }

        int getInt(const std::vector<Value *> &indices = {})
        {
            if (indices.empty())
            {
                return intScalarValue;
            }
            else
            {
                int factor = 1;
                int offset = 0;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                return intArrayData[offset];
            }
        }

        int getFloat(const std::vector<Value *> &indices = {})
        {
            if (indices.empty())
            {
                return floatScalarValue;
            }
            else
            {
                int factor = 1;
                int offset = 0;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                return floatArrayData[offset];
            }
        }

        std::string getArrayTypeStr()
        {
            std::stringstream ss;
            std::string ty = type->as<PointerType>()->getBaseType()->as<PointerType>()
                ? type->as<PointerType>()->getBaseType()->as<PointerType>()->getBaseType()->toString()
                : type->as<PointerType>()->getBaseType()->as<ArrayType>()->getBaseType()->toString();
            //            if (type->as<PointerType>()->getBaseType()->isInt())
            //            {
            //                ty = "i32";
            //            }
            //            else
            //            {
            //                ty = "float";
            //            }

            for (int i = nDims - 1; i >= 0; i--)
            {
                ss.str("");
                int len = dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                ss << "[" << len << " x " << ty << "]";
                ty = ss.str();
            }
            return ty;
        }
    }; // class AllocaInst

    //! Load a value from memory address specified by a pointer value
    class LoadInst : public Instruction
    {
        friend class IRBuilder;

    protected:
        LoadInst(Value *pointer, BasicBlock *parent = nullptr, const std::string &name = "") :
            Instruction(kLoad, pointer->getType()->as<PointerType>()->getBaseType(), parent, name)
        {
            addOperand(pointer);
        }

    public:
        int getNumIndices() const { return getNumOperands() - 1; }

        Value *getPointer() const { return getOperand(0); }

        auto getIndices() const { return make_range(std::next(operand_begin()), operand_end()); }

        Value *getIndex(int index) const { return getOperand(index + 1); }

        const std::string toString() override;
    }; // class LoadInst

    //! Store a value to memory address specified by a pointer value
    class StoreInst : public Instruction
    {
        friend class IRBuilder;

    protected:
        StoreInst(Value *value, Value *pointer,
                  //              const std::vector<Value*>& indices = {},
                  BasicBlock *parent = nullptr, const std::string &name = "") :
            Instruction(kStore, Type::getVoidType(), parent, name)
        {
            addOperand(value);
            addOperand(pointer);
            //        addOperands(indices);
        }

    public:
        int getNumIndices() const { return getNumOperands() - 2; }

        Value *getValue() const { return getOperand(0); }

        Value *getPointer() const { return getOperand(1); }

        auto getIndices() const { return make_range(operand_begin() + 2, operand_end()); }

        Value *getIndex(int index) const { return getOperand(index + 2); }

        const std::string toString() override;
    }; // class StoreInst

    // GEP指令的类型一定是i32*/float*而不是array*
    class GEPInst : public Instruction
    {
        friend class IRBuilder;

        static Type *getGEPRetType(Type *ty)
        {
            assert(ty->isPointer());
            ty = ty->as<PointerType>()->getBaseType();
            if (ty->isArray())
            {
                ty = ty->as<ArrayType>()->getBaseType();
            }
            return Type::getPointerType(ty);
        }

    protected:
        GEPInst(Value *pointer, std::vector<Value *> &indices, BasicBlock *parent, const std::string &name) :
            Instruction(kGEP, getGEPRetType(pointer->getType()), parent, name)
        {
            addOperand(pointer);
            addOperands(indices);
        }

    public:
        const std::string toString() override;
    };

    class Module;

    //! Function definition
    class Function : public Value
    {
        friend class Module;

    protected:
        Function(Module *parent, Type *type, const std::string &name, bool onlyDefinition = false) :
            Value(type, name), parent(parent), blocks(), exitBlocks(), retValue(nullptr), funcExitBlock(nullptr)
        {
            if (!onlyDefinition)
                blocks.emplace_back(new BasicBlock(this, "entry_" + name));
        }

    public:
        using block_list = std::list<std::unique_ptr<BasicBlock>>;

    protected:
        Module *parent;
        block_list blocks;
        block_list exitBlocks;
        std::vector<Argument> args;
        Value *retValue;
        BasicBlock *funcExitBlock;

    public:
        [[nodiscard]] Value *getRetValue() const { return retValue; }

        void setRetValue(Value *ret_value) { retValue = ret_value; }

        Type *getReturnType() const { return getType()->as<FunctionType>()->getReturnType(); }

        auto getParamTypes() const { return getType()->as<FunctionType>()->getParamTypes(); }

        auto getBasicBlocks() { return make_range(blocks); }

        auto &getBBlocks() { return blocks; }

        BasicBlock *getEntryBlock() { return blocks.front().get(); }

        BasicBlock *getExitBlock() { return blocks.back().get(); }

        BasicBlock *addBasicBlock(const std::string &name = "")
        {
            blocks.emplace_back(new BasicBlock(this, name));
            return blocks.back().get();
        }

        BasicBlock *addExitBlock(const std::string &name = "")
        {
            exitBlocks.emplace_back(new BasicBlock(this, name));
            return exitBlocks.back().get();
        }

        void removeBasicBlock(BasicBlock *block)
        {
            blocks.remove_if([&](std::unique_ptr<BasicBlock> &b) -> bool { return block == b.get(); });
        }

        void moveExitBlock()
        {
            blocks.emplace_back(std::move(exitBlocks.back()));
            exitBlocks.pop_back();
        }

        auto &getArguments() { return args; }

        void createArgument(Type *type, const std::string &name) { args.emplace_back(type, name); }

        BasicBlock *createFuncExitBlock()
        {
            funcExitBlock = new BasicBlock(this, name + "_exit");
            return funcExitBlock;
        }

        BasicBlock *getFuncExitBlock() const { return funcExitBlock; }

        void removeFuncExitBlock()
        {
            delete funcExitBlock;
            funcExitBlock = nullptr;
        }

        void keepFuncExitBlock() { blocks.emplace_back(funcExitBlock); }

        friend std::ostream &operator<<(std::ostream &os, Function &f)
        {
            // TODO: 函数参数
            os << f.getReturnType()->toString() << " @" << f.name << "(";
            if (!f.getArguments().empty())
            {
                auto args = f.getArguments();
                os << args[0].getType()->toString() << " " << args[0].getName();
                for (int i = 1; i != args.size(); i++)
                    os << ", " << args[i].getType()->toString() << " " << args[i].getName();
            }
            os << ")";
            return os;
        }
    }; // class Function

    //! Global value declared at file scope
    class GlobalValue : public User
    {
    public:
        ~GlobalValue() override
        {
            if (intArrayData)
                delete[] intArrayData;
            else
                delete[] floatArrayData;
        };

        friend class Module;

    protected:
        Module *parent;
        bool hasInit;
        bool isConst;

        // TODO: globalvalue是const时, 保存常量值的成员

        // 数组的维数, 可以通过这个判断是数组还是标量
        int nDims;
        // 数组大小
        int arraySize;
        // 数组指针
        // std::variant<int, float>* arrayData;
        int *intArrayData;
        float *floatArrayData;
        // 标量值
        // std::variant<int, float> scalarValue;
        int intScalarValue;
        float floatScalarValue;

    protected:
        GlobalValue(Module *parent, Type *type, const std::string &name, const std::vector<Value *> &dims = {},
                    Value *init = nullptr, bool isConst = false, int arraySize = 0) :
            User(type, name), parent(parent), hasInit(init), isConst(isConst), nDims(dims.size()), arraySize(arraySize),
            intArrayData(nullptr), floatArrayData(nullptr)
        {
            assert(type->isPointer());
            addOperands(dims);
            if (!dims.empty())
            {
                if (type->as<PointerType>()->getBaseType()->as<ArrayType>()->getBaseType()->isInt())
                {
                    intArrayData = new int[arraySize];
                    memset(intArrayData, 0, arraySize * sizeof(int));
                }
                else
                {
                    floatArrayData = new float[arraySize];
                    memset(floatArrayData, 0, arraySize * sizeof(float));
                }
            }
            else
            {
                if (type->as<PointerType>()->getBaseType()->isInt())
                {
                    intScalarValue = 0;
                }
                else
                {
                    floatScalarValue = 0.0;
                }
            }
            if (init)
            {
                addOperand(init);
                if (!dims.empty())
                {
                    // if (type->as<PointerType>()->getBaseType()->isInt()) {
                    //     intArrayData = new int[arraySize];
                    //     memset(intArrayData, 0, arraySize);
                    // }
                    // else {
                    //     floatArrayData = new float[arraySize];
                    //     memset(floatArrayData, 0, arraySize);
                    // }
                }
                else
                {
                    // TODO: 测试正确性
                    if (type->as<PointerType>()->getBaseType()->isInt())
                    {
                        intScalarValue = dynamic_cast<ConstantValue *>(init)->getInt();
                    }
                    else
                    {
                        floatScalarValue = dynamic_cast<ConstantValue *>(init)->getFloat();
                    }
                }
            }
        }

    public:
        Value *init() const { return hasInit ? operands.back().getValue() : nullptr; }

        int getNumDims() const { return nDims; }

        Value *getDim(int index) { return getOperand(index); }

        bool isconst() { return isConst; }

        void setInt(int v, const std::vector<Value *> &indices = {})
        {
            if (indices.empty())
            {
                intScalarValue = v;
            }
            else
            {
                int offset = 0, factor = 1;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                intArrayData[offset] = v;
            }
        }

        void setInt(int v, const std::vector<int> &indices)
        {
            if (indices.empty())
            {
                intScalarValue = v;
            }
            else
            {
                int offset = 0, factor = 1;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * indices[i];
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                intArrayData[offset] = v;
            }
        }

        int getInt(const std::vector<Value *> &indices = {})
        {
            int factor = 1;
            int offset = 0;
            for (int i = indices.size() - 1; i >= 0; i--)
            {
                offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
            }
            return intArrayData[offset];
        }

        void setFloat(float v, const std::vector<Value *> &indices = {})
        {
            if (indices.empty())
            {
                floatScalarValue = v;
            }
            else
            {
                int offset = 0, factor = 1;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                floatArrayData[offset] = v;
            }
        }

        void setFloat(float v, const std::vector<int> &indices = {})
        {
            if (indices.empty())
            {
                floatScalarValue = v;
            }
            else
            {
                int offset = 0, factor = 1;
                for (int i = indices.size() - 1; i >= 0; i--)
                {
                    offset += factor * indices[i];
                    factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                }
                floatArrayData[offset] = v;
            }
        }

        float getFloat(const std::vector<Value *> &indices = {})
        {
            int factor = 1;
            int offset = 0;
            for (int i = indices.size() - 1; i >= 0; i--)
            {
                offset += factor * dynamic_cast<ConstantValue *>(indices[i])->getInt();
                factor *= dynamic_cast<ConstantValue *>(getDim(i))->getInt();
            }
            return floatArrayData[offset];
        }

        friend std::ostream &operator<<(std::ostream &os, GlobalValue &gv)
        {
            os << gv.name << " = dso_local ";
            if (gv.isConst)
            {
                os << "constant ";
            }
            else
            {
                os << "global ";
            }
            if (gv.nDims == 0)
            {
                // scalar
                if (gv.type->as<PointerType>()->getBaseType()->isInt())
                {
                    os << "i32 " << dynamic_cast<ConstantValue *>(gv.init())->getName();
                }
                else
                {
                    os << "float " << dynamic_cast<ConstantValue *>(gv.init())->getName();
                }
            }
            else
            {
                os << gv.genArrayValueStr();
            }
            return os;
        }

    private:
        std::string genArrayValueStr()
        {
            auto tohex = [](float input)
            {
                double i = input;
                auto ii = *(unsigned long long *)&i;
                std::ostringstream o_s;
                o_s << std::hex << ii;
                auto name = o_s.str();
                while (name.size() < 16)
                    name = "0" + name;
                //        auto name = std::to_string(*(unsigned long long *) &i);
                return "0x" + name;
            };

            std::stringstream ss;
            std::stringstream tmp;
            std::string type;

            if (!hasInit)
            {
                ss.str("");
                ss << getArrayTypeStr() << " zeroinitializer";
                return ss.str();
            }

            bool isInt = false;
            if (this->type->as<PointerType>()->getBaseType()->as<ArrayType>()->getBaseType()->isInt())
            {
                type = "i32";
                isInt = true;
            }
            else
            {
                type = "float";
            }

            std::vector<std::string> rawValueStrs;

            if (isInt)
            {
                for (int i = 0; i < arraySize; i++)
                {
                    tmp.str("");
                    tmp << type << " " << std::to_string(intArrayData[i]);
                    rawValueStrs.emplace_back(tmp.str());
                }
            }
            else
            {
                for (int i = 0; i < arraySize; i++)
                {
                    tmp.str("");
                    tmp << type << " " << tohex(floatArrayData[i]);
                    rawValueStrs.emplace_back(tmp.str());
                }
            }

            std::vector<std::string> totalValueNames;


            int size = arraySize;
            for (int i = nDims - 1; i >= 0; i--)
            {
                auto n = dynamic_cast<ConstantValue *>(operands[i].getValue())->getInt();

                tmp.str("");
                tmp << "[" << n << " x " << type << "]";
                type = tmp.str();
                size /= n;

                for (int j = 0; j < size; j++)
                {
                    tmp.str("");
                    tmp << type << " [" << rawValueStrs[j * n];
                    for (int k = 1; k < n; k++)
                    {
                        tmp << ", " << rawValueStrs[j * n + k];
                        // tmp << ", " << rawValueStrs[j / step + k];
                    }
                    tmp << "]";
                    totalValueNames.emplace_back(tmp.str());
                }

                rawValueStrs = totalValueNames;
                totalValueNames.clear();
            }
            return rawValueStrs[0];
        }

    public:
        std::string getArrayTypeStr()
        {
            std::stringstream ss;
            std::string ty = type->as<PointerType>()->getBaseType()->as<ArrayType>()->getBaseType()->toString();

            for (int i = nDims - 1; i >= 0; i--)
            {
                ss.str("");
                int len = dynamic_cast<ConstantValue *>(getDim(i))->getInt();
                ss << "[" << len << " x " << ty << "]";
                ty = ss.str();
            }
            return ty;
        }
    }; // class GlobalValue

    //! IR unit for representing a SysY compile unit
    class Module
    {
    protected:
        std::map<std::string, std::unique_ptr<Function>> functions;
        std::map<std::string, std::unique_ptr<GlobalValue>> globals;

        std::vector<std::string> funcNames;
        std::vector<std::string> globalNames;

    public:
        Module() = default;

        const auto &getFuncNames() const { return funcNames; }
        const auto &getGlobalNames() const { return globalNames; }

    public:
        Function *createFunction(const std::string &name, Type *type, bool onlyDefinition = false)
        {
            auto result = functions.try_emplace(name, new Function(this, type, name, onlyDefinition));
            if (not result.second)
                return nullptr;
            funcNames.emplace_back(name);
            return result.first->second.get();
        };
        // 是否缺少了创建初始值的部分?
        // 根据https://gitlab.eduxiji.net/educg-group-18973-1895971/202390002201726-1057/-/blob/main/src/IR.h
        // 确实是这样
        // 参考学长代码修改, 添加初始值和const参数
        // 配套的isConst方法和GlobalValue构造函数修改同
        // TODO: 全局变量是否一定要有默认值?
        GlobalValue *createGlobalValue(const std::string &name, Type *type, const std::vector<Value *> &dims = {},
                                       // 几乎所有的dims参数指的都是数组的维度, 例如int传空, int[2]传{2}
                                       Value *init = nullptr, bool isConst = false, int arraySize = 0)
        {
            // result的类型是pair<map::iterator, bool>
            // 后者表示是否发生插入,
            // 前者指向已存在的元素(未进行插入动作)或新插入的元素(执行插入动作)
            auto result = globals.try_emplace(name, new GlobalValue(this, type, name, dims, init, isConst, arraySize));
            if (not result.second)
                return nullptr;
            globalNames.emplace_back(name);
            return result.first->second.get();
        }

        Function *getFunction(const std::string &name) const
        {
            auto result = functions.find(name);
            if (result == functions.end())
                return nullptr;
            return result->second.get();
        }

        GlobalValue *getGlobalValue(const std::string & name) const
        {
            auto result = globals.find(name);
            if (result == globals.end())
                return nullptr;
            return result->second.get();
        }

        const auto& getGlobalValue() { return globals; }

        const auto& getFunctions() { return functions; }
    }; // class Module

    /*!
     * @}
     */
} // namespace sysy
